#include "database_mysql.h"

namespace engine
{
	static int mysql_library_flag = 0;

	//////////////////////////////////////////////////////////////////////////
	void DatabaseMySQL::initialize(const MySQLDesc& desc)
	{
		printf("===========================================\n");
		printf("Initializing MySQL Database\n");
		printf("===========================================\n");

		if (mysql_library_flag == 0)
		{
			mysql_library_init(-1, NULL, NULL);
		}

		mysql_library_flag++;

		if (!mysql_thread_safe())
		{
			printf("FATAL ERROR: Used MySQL library isn't thread-safe.\n");
			exit(1);
		}

		printf("Connecting to MySQL database at %s\n", desc.host.c_str());
		printf("MySQL client library: %s\n", mysql_get_client_info());

		for (uint32 i = 0; i < desc.threadCount; i++)
		{
			MYSQL* mysqlInit = mysql_init(NULL);

			char value = 1;
			if (mysql_options(mysqlInit, MYSQL_OPT_RECONNECT, (char*)&value))
			{
				printf("Failed to set MYSQL_OPT_RECONNECT option\n");
			}

			MYSQL* mysqlConn = mysql_real_connect(mysqlInit, desc.host.c_str(), desc.user.c_str(),
				desc.password.c_str(), desc.database.c_str(), desc.port, NULL, CLIENT_MULTI_RESULTS);

			if (mysqlConn)
			{
				mysql_query(mysqlConn, "SET NAMES 'utf8';");

				if (i == 0)
				{
					printf("MySQL server ver: %s\n", mysql_get_server_info(mysqlConn));
				}

				printf("Connection[%d] OK, [%s]\n", i, mysql_get_host_info(mysqlConn));

				DatabaseThreadMySQL* th = new DatabaseThreadMySQL();
				th->mMysql = mysqlConn;
				mThreads.push_back(th);
			}
			else
			{
				printf("Could not connect to MySQL database at %s: %s\n", desc.host.c_str(), mysql_error(mysqlInit));
				mysql_close(mysqlInit);
				exit(1);
			}
		}

		for (uint32 i = 0; i < desc.threadCount; i++)
		{
			mThreads[i]->start();
		}
	}

	void DatabaseMySQL::finalize()
	{
		for (size_t i = 0; i < mThreads.size(); i++)
		{
			DatabaseThreadMySQL* th = static_cast<DatabaseThreadMySQL*>(mThreads[i]);
			th->stop();
			mysql_close(th->mMysql);
			delete th;
		}

		mThreads.clear();

		mysql_library_flag--;

		if (mysql_library_flag == 0)
		{
			mysql_library_end();
		}
	}

	//////////////////////////////////////////////////////////////////////////
	void* DatabaseThreadMySQL::entry()
	{
		mysql_thread_init();

		void* ret = DatabaseThread::entry();

		mysql_thread_end();

		return ret;
	}

	QueryResultSet* DatabaseThreadMySQL::query(const String& sql)
	{
		MYSQL_RES* result = NULL;
		uint32 rowCount = 0;
		uint32 fieldCount = 0;

		if (mysql_real_query(mMysql, sql.c_str(), (unsigned long)sql.length()))
		{
			printf("SQL: %s\n", sql.c_str());
			printf("SQL ERROR: %s\n", mysql_error(mMysql));
			return NULL;
		}

		int status = 0;
		QueryResultSet* resultSet = new QueryResultSet();

		do
		{
			result = mysql_store_result(mMysql);

			if (result)
			{
				rowCount = (uint32)mysql_num_rows(result);
				fieldCount = (uint32)mysql_num_fields(result);

				QueryResultMySQL *queryResult = new QueryResultMySQL(result, rowCount, fieldCount);
				queryResult->nextRow();
				resultSet->pushResult(queryResult);
			}
			else
			{
				resultSet->pushResult(NULL);
			}

			if ((status = mysql_next_result(mMysql)) > 0)
			{
				printf("SQL: %s\n", sql.c_str());
				printf("SQL ERROR: %s\n", mysql_error(mMysql));
			}

		}
		while (status == 0);

		return resultSet;
	}

	bool DatabaseThreadMySQL::execute(const String& sql)
	{
		if (mysql_real_query(mMysql, sql.c_str(), (unsigned long)sql.length()))
		{
			printf("SQL: %s\n", sql.c_str());
			printf("SQL ERROR: %s\n", mysql_error(mMysql));
			return false;
		}

		int status = 0;

		do
		{
			mysql_free_result(mysql_use_result(mMysql));
			status = mysql_next_result(mMysql);
		}
		while (status == 0);

		return true;
	}

	//////////////////////////////////////////////////////////////////////////
	QueryResultMySQL::QueryResultMySQL(MYSQL_RES *result, uint32 rowCount, uint32 fieldCount)
		: QueryResult(rowCount, fieldCount), mResult(result)
	{
		MYSQL_FIELD *fd = NULL;
		mCurrentRow = new DataField[mFieldCount];

		for(uint32 i = 0; i < mFieldCount; i++)
		{
			fd = mysql_fetch_field(result);
			mCurrentRow[i].setType(convertNativeType(fd->type));
		}
	}

	QueryResultMySQL::~QueryResultMySQL()
	{
		endQuery();
	}

	bool QueryResultMySQL::nextRow()
	{
		MYSQL_ROW row;

		if (!mResult)
			return false;

		row = mysql_fetch_row(mResult);
		if (!row)
		{
			endQuery();
			return false;
		}

		for (uint32 i = 0; i < mFieldCount; i++)
			mCurrentRow[i].setValue(row[i]);

		return true;
	}

	void QueryResultMySQL::endQuery()
	{
		if (mCurrentRow)
		{
			delete [] mCurrentRow;
			mCurrentRow = 0;
		}

		if (mResult)
		{
			mysql_free_result(mResult);
			mResult = 0;
		}
	}

	enum DataField::DataTypes QueryResultMySQL::convertNativeType(enum_field_types mysqlType) const
	{
		switch (mysqlType)
		{
		case FIELD_TYPE_TIMESTAMP:
		case FIELD_TYPE_DATE:
		case FIELD_TYPE_TIME:
		case FIELD_TYPE_DATETIME:
		case FIELD_TYPE_YEAR:
		case FIELD_TYPE_STRING:
		case FIELD_TYPE_VAR_STRING:
		case FIELD_TYPE_BLOB:
		case FIELD_TYPE_SET:
		case FIELD_TYPE_NULL:
			return DataField::DB_TYPE_STRING;

		case FIELD_TYPE_TINY:
		case FIELD_TYPE_SHORT:
		case FIELD_TYPE_LONG:
		case FIELD_TYPE_INT24:
		case FIELD_TYPE_LONGLONG:
		case FIELD_TYPE_ENUM:
			return DataField::DB_TYPE_INTEGER;

		case FIELD_TYPE_DECIMAL:
		case FIELD_TYPE_FLOAT:
		case FIELD_TYPE_DOUBLE:
			return DataField::DB_TYPE_FLOAT;

		default:
			return DataField::DB_TYPE_UNKNOWN;
		}
	}
}
